/*
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.io;

import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Variable encoding of an integer, the length of a byte array.  The
 * encoded integer is represented in network byte order in one to four
 * bytes, with a minimum encoded value of zero, and a maximum encoded
 * value of <i>(2^28)-1</i> or <i>268,435,455</i>.  
 * 
 * <h3>Format</h3>
 * 
 * The high bit of each byte is used as a continuation flag, with a
 * maximum of four bytes in the list of octets.  Each octet in the
 * field excludes the high (continuation) bit from the encoded value.
 * 
 * @author jdp
 */
public class Varlen4 {

    public final static int MIN = 0;
    /**
     * Four seven bit words contain <i>(2^28)-1</i> possible
     * values.
     */
    public final static int MAX = 268435455;


    /**
     * Validate payload bits.
     * @param data The length of payload data to be represented with
     * this technique
     * @return True when the length of the argument array is within
     * the range of representation.
     */
    public final static boolean IsValid(byte[] data){
        if (null == data)
            return true;
        else {
            return (MAX >= data.length);
        }
    }
    /**
     * Validate payload bits.
     * @param data The length of payload data to be represented with
     * this technique
     * @return True when the length of the argument array is greater
     * than the range of representation.
     */
    public final static boolean IsNotValid(byte[] data){
        if (null == data)
            return false;
        else {
            return (MAX < data.length);
        }
    }
    /**
     * Decode from stream.
     */
    public final static int Decode(InputStream in)
        throws IOException
    {
        int a = in.read();
        if (0 != (a & 0x80)){
            a &= 0x7f;
            int b = in.read();
            if (0 != (b & 0x80)){
                b &= 0x7f;
                int c = in.read();
                if (0 != (c & 0x80)){
                    c &= 0x7f;
                    int d = (in.read() & 0x7f);
                    return (a << 21)|(b << 14)|(c << 7)|(d);
                }
                else {
                    return (a << 14)|(b << 7)|(c);
                }
            }
            else {
                return (a << 7)|(b);
            }
        }
        else
            return a;
    }
    /**
     * Decode from offset. 
     */
    public final static class Decode 
        extends Object 
    {
        public final int count, value;
        /**
         * Decode values in series
         */
        public Decode(Decode a, Decode b, byte[] data){
            this((a.count+b.count),data);
        }
        /**
         * Decode a value in series
         */
        public Decode(Decode b, byte[] data){
            this(b.count,data);
        }
        /**
         * Decode value from offset zero.
         */
        public Decode(byte[] data){
            this(0,data);
        }
        /**
         * Decode value from offset.
         */
        public Decode(int ofs, byte[] data){
            super();
            int a = data[ofs];
            if (0 != (a & 0x80)){
                a &= 0x7f;
                int b = data[ofs+1];
                if (0 != (b & 0x80)){
                    b &= 0x7f;
                    int c = data[ofs+2];
                    if (0 != (c & 0x80)){
                        c &= 0x7f;
                        int d = (data[ofs+3] & 0x7f);
                        this.count = 4;
                        this.value = (a << 21)|(b << 14)|(c << 7)|(d);
                    }
                    else {
                        this.count = 3;
                        this.value = (a << 14)|(b << 7)|(c);
                    }
                }
                else {
                    this.count = 2;
                    this.value = (a << 7)|(b);
                }
            }
            else {
                this.count = 1;
                this.value = a;
            }
        }

    }
    /**
     * Encode to byte array.
     */
    public final static byte[] Encode(int value){

        int a = ((value >>>21) & 0x7f);
        int b = ((value >>>14) & 0x7f);
        int c = ((value >>> 7) & 0x7f);
        int d =  (value & 0x7f);
        if (0 != a){
            byte[] re = new byte[4];
            re[0] = (byte)(a | 0x80);
            re[1] = (byte)(b | 0x80);
            re[2] = (byte)(c | 0x80);
            re[3] = (byte)(d);
            return re;
        }
        else if (0 != b){
            byte[] re = new byte[3];
            re[0] = (byte)(b | 0x80);
            re[1] = (byte)(c | 0x80);
            re[2] = (byte)(d);
            return re;
        }
        else if (0 != c){
            byte[] re = new byte[2];
            re[0] = (byte)(c | 0x80);
            re[1] = (byte)(d);
            return re;
        }
        else {
            return new byte[]{(byte)d};
        }
    }
    /**
     * Encode to stream
     */
    public final static void Encode(OutputStream out, int value)
        throws IOException
    {
        int a = ((value >>>21) & 0x7f);
        int b = ((value >>>14) & 0x7f);
        int c = ((value >>> 7) & 0x7f);
        int d =  (value & 0x7f);
        if (0 != a){
            out.write(a | 0x80);
            out.write(b | 0x80);
            out.write(c | 0x80);
            out.write(d);
        }
        else if (0 != b){
            out.write(b | 0x80);
            out.write(c | 0x80);
            out.write(d);
        }
        else if (0 != c){
            out.write(c | 0x80);
            out.write(d);
        }
        else
            out.write(d);
    }
}
